﻿// Nova.CLI - a command-line interface for Nova.CodeDOM.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;

using Nova.CodeDOM;
using Nova.Utilities;

namespace Nova.CLI
{
    /// <summary>
    /// This program is a command-line interface for the Nova.CodeDOM C# object model library.
    /// </summary>
    class Program
    {
        static void Main(string[] arguments)
        {
            int exitCode = 0;
            try
            {
                // Determine the application name and if we're running in VS
                string appName = AppDomain.CurrentDomain.FriendlyName;
                if (appName.EndsWith(".exe"))
                    appName = appName.Substring(0, appName.Length - 4);
                if (appName.EndsWith(".vshost"))
                    appName = appName.Substring(0, appName.Length - 7);
                bool waitBeforeExiting = Debugger.IsAttached;

                bool spawned = StringUtil.Contains(arguments, "/spawned");
                if (!spawned)
                {
                    Version appVersion = typeof(Program).Assembly.GetName().Version;
                    string appDescription = appName + " " + appVersion.Major + "." + appVersion.Minor;
                    Console.WriteLine(appDescription + "  -  C# CodeDOM command-line utility.\nCopyright (C) 2011-2012 Inevitable Software, all rights reserved.");
                }

                // Process the command-line arguments
                if (arguments == null || arguments.Length == 0 || (arguments.Length == 1 && arguments[0] == "/?"))
                {
                    Console.WriteLine("\nUsage: " + appName + " <file> [<file> ...] [<option> ...]\n"
                        + "                ('<file>' is a C# source file)\n"
                        + "\n"
                        + "\tGeneral options:\n"
                        + "\t/save         - Save all files after loading is complete\n"
                        + "\t/detail       - Detailed log messages\n"
                        + "\t/minimal      - Minimal log messages, and no code messages\n"
                        + "\t/spawn        - Load each specified file using a child process\n"
                        + "\t/wait         - Wait for key press when done before exiting\n"
                        + "\t/waitfail     - Wait for key press when done if any check option failed\n"
                        + "\t/donotparsebodies   - Don't parse method bodies\n"
                        + "\n"
                        + "\tOptions for the preceeding file only:\n"
                        + "\t/check:E,W,C  - Check message counts (E=errors, W=warnings, C=comments)\n"
                        );
                }
                else
                    exitCode = ProcessArguments(arguments, appName, ref waitBeforeExiting);

                // Wait for a key press before exiting if requested
                if (waitBeforeExiting)
                {
                    Console.WriteLine("\nPress any key to exit.");
                    Console.ReadKey();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("EXCEPTION: " + ex);
            }

            Environment.Exit(exitCode);
        }

        private static int ProcessArguments(string[] arguments, string appName, ref bool waitBeforeExiting)
        {
            bool spawn = false;
            bool spawned = false;
            string spawnArguments = null;
            bool save = false;
            bool waitIfChecksFail = false;
            LoadOptions loadOptions = LoadOptions.Complete;

            // Process any options first
            foreach (string argument in arguments)
            {
                // Look only at options here
                if (argument.StartsWith("/") && !argument.StartsWith("/check:"))
                {
                    switch (argument.Substring(1).ToLower())
                    {
                        case "donotparsebodies": loadOptions |= LoadOptions.DoNotParseBodies; break;
                        case "save": save = true; break;
                        case "detail": Log.LogLevel = Log.Level.Detailed; break;
                        case "minimal": Log.LogLevel = Log.Level.Minimal; break;
                        case "spawn":
                            spawn = true;
                            spawnArguments = Enumerable.Aggregate(Enumerable.Where(arguments,
                                delegate(string arg) { return arg.StartsWith("/") && arg != "/spawn" && arg != "/wait" && !arg.StartsWith("/check:"); }), spawnArguments,
                                delegate(string current, string arg) { return current + (arg + " "); }) + "/spawned";
                            Log.WriteLine("Spawning each load in a separate child process...");
                            break;
                        case "wait": waitBeforeExiting = true; break;
                        case "waitfail": waitIfChecksFail = true; break;
                        case "spawned": spawned = true; Configuration.LogSettings = false; break;
                        default:
                            Log.WriteLine("Option '" + argument + "' not recognized (use no arguments to get a list of valid ones)."); break;
                    }
                }
            }

            // Process any specified solution, project, or source files
            int fileCount = 0, checkCount = 0, failCount = 0;
            int errorCount = 0, warningCount = 0, commentCount = 0;
            for (int index = 0; index < arguments.Length; ++index)
            {
                string argument = arguments[index];
                if (!argument.StartsWith("/"))  // Ignore options at this point
                {
                    if (spawn)
                    {
                        // If so requested, run each file load in a separate child process
                        Log.WriteLine("");
                        string spawnArgs = "\"" + argument + "\" ";
                        bool checkMessageCounts = false;
                        if (arguments[index + 1].StartsWith("/check:"))
                        {
                            ++index;
                            spawnArgs += arguments[index] + " ";
                            checkMessageCounts = true;
                        }
                        spawnArgs += spawnArguments;
                        Process process = new Process { StartInfo = { FileName = appName + ".exe", Arguments = spawnArgs, UseShellExecute = false } };
                        process.Start();
                        process.WaitForExit();
                        if (checkMessageCounts)
                        {
                            ++checkCount;
                            if (process.ExitCode != 0)
                                ++failCount;
                        }
                    }
                    else
                    {
                        if (!spawned)
                            Log.WriteLine("");
                        if (Log.LogLevel == Log.Level.Detailed)
                        {
                            GC.Collect();
                            Log.DetailWriteLine("Before Heap Size = " + (int)(GC.GetTotalMemory(true) / (1024 * 1024)) + " MBs");
                        }

                        // Get the file extension, defaulting to ".cs" if none
                        string fileName = argument;
                        string fileExtension = Path.GetExtension(fileName);
                        if (string.IsNullOrEmpty(fileExtension))
                        {
                            fileExtension = ".cs";
                            fileName += fileExtension;
                        }

                        // Load the specified file
                        loadOptions |= LoadOptions.LogMessages;
                        if (fileExtension == ".cs")
                        {
                            CodeUnit codeUnit = CodeUnit.Load(fileName, loadOptions);
                            if (codeUnit != null)
                            {
                                codeUnit.GetMessageCounts(out errorCount, out warningCount, out commentCount);
                                if (save)
                                    codeUnit.Save();
                            }
                        }

                        if (Log.LogLevel == Log.Level.Detailed)
                        {
                            GC.Collect();
                            Log.DetailWriteLine("After Heap Size = " + (int)(GC.GetTotalMemory(true) / (1024 * 1024)) + " MBs");
                        }
                    }
                    ++fileCount;
                }
                else if (argument.StartsWith("/check:"))
                {
                    ++checkCount;
                    bool pass = false;
                    int[] counts = StringUtil.ParseArray<int>(argument.Substring(7), null);
                    if (counts != null && counts.Length > 0)
                    {
                        pass = ((counts[0] == errorCount) && (counts.Length < 2 || counts[1] == warningCount)
                            && (counts.Length < 3 || counts[2] == commentCount));
                    }
                    if (!pass)
                        ++failCount;
                    Log.WriteLine("Check message counts: " + (pass ? "PASS" : "FAIL"));
                }
            }

            Log.WriteLine("");
            if (fileCount > 1)
            {
                GC.Collect();
                Log.WriteLine("DONE!  " + fileCount + " files loaded.  Heap size = " + (int)(GC.GetTotalMemory(true) / (1024 * 1024)) + " MBs");

                if (checkCount > 0)
                {
                    if (!spawned)
                        Log.WriteLine("Checked " + checkCount + ", " + (failCount == 0 ? "all PASSED" : "FAILED " + failCount));
                    if (waitIfChecksFail && failCount > 0)
                        waitBeforeExiting = true;
                }
            }

            return -failCount;
        }
    }
}
